package com.spzx.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.spzx.cart.api.RemoteCartService;
import com.spzx.cart.api.domain.CartInfo;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.core.utils.bean.BeanUtils;
import com.spzx.common.rabbit.constant.MqConst;
import com.spzx.common.rabbit.service.RabbitService;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.order.api.domain.OrderInfo;
import com.spzx.order.api.domain.OrderItem;
import com.spzx.order.domain.OrderLog;
import com.spzx.order.form.OrderForm;
import com.spzx.order.mapper.OrderInfoMapper;
import com.spzx.order.mapper.OrderItemMapper;
import com.spzx.order.mapper.OrderLogMapper;
import com.spzx.order.service.IOrderInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.order.vo.TradeVo;
import com.spzx.payment.api.RemotePaymentInfoService;
import com.spzx.product.api.RemoteProductService;
import com.spzx.product.api.RemoteSkuStockService;
import com.spzx.product.api.vo.SkuLockVo;
import com.spzx.product.api.vo.SkuPriceVo;
import com.spzx.product.api.vo.ProductSkuVo;
import com.spzx.user.api.RemoteUserAddressService;
import com.spzx.user.api.domain.UserAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 订单 服务实现类
 * </p>
 *
 * @author mcf
 * @since 2024-12-27
 */
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements IOrderInfoService {

    @Autowired
    private RemoteCartService remoteCartService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RemoteProductService remoteProductService;

    @Autowired
    private RemoteUserAddressService remoteUserAddressService;

    @Autowired
    private OrderLogMapper orderLogMapper;

    @Autowired
    private RemoteSkuStockService remoteSkuStockService;

    @Autowired
    private RabbitService rabbitService;
    /**
     * 渲染订单确认页面-生成用户流水号
     *
     * @param userId
     * @return
     */
    private String generateTradeNo(Long userId) {
        //1.构建流水号Key
        String userTradeKey = "user:"+ userId +":tradeNo" ;
        //2.构建流水号value
        String tradeNo = UUID.randomUUID().toString().replaceAll("-", "");
        //3.将流水号存入Redis 暂存5分钟
        redisTemplate.opsForValue().set(userTradeKey, tradeNo, 5, TimeUnit.MINUTES);
        return tradeNo;
    }


//    @Autowired
//    private RemotePaymentInfoService remotePaymentInfoService;

    /**
     * 生成订单交易数据
     *
     * 此方法用于生成订单交易数据，包括获取当前登录用户的购物车信息，
     * 计算订单总金额，并生成唯一的交易编号，以防止重复提交和超时问题
     *
     * @return TradeVo 返回封装了交易信息的TradeVo对象，包括总金额、订单项列表和交易编号
     * @throws ServiceException 如果购物车列表为空或获取购物车信息失败，则抛出服务异常
     */
    @Override
    public TradeVo orderTradeData() {
        // 获取当前登录用户的id
        Long userId = SecurityUtils.getLoginUser().getUserid();
        // 调用购物车服务获取当前登录用户的购物车数据
        R<List<CartInfo>> cartInfoListResult = remoteCartService.getCartCheckedList(SecurityConstants.INNER);
        // 判断购物车列表是否为空
        if (R.FAIL == cartInfoListResult.getCode()) {
            throw new ServiceException(cartInfoListResult.getMsg());
        }
        // 获取购物车列表
        List<CartInfo> cartInfoList = cartInfoListResult.getData();
        if (CollectionUtils.isEmpty(cartInfoList)) {
            throw new ServiceException("购物车无选中商品");
        }
        //将集合泛型从购物车改为订单明细
        List<OrderItem> orderItemList = null;
        BigDecimal totalAmount = new BigDecimal(0);
        // 判断购物车列表是否为空
        if (!CollectionUtils.isEmpty(cartInfoList)) {
            // 将购物车列表中的购物项转化为订单明细
            orderItemList = cartInfoList
                    .stream()
                    .map(cartInfo -> {
                        OrderItem orderItem = new OrderItem();
                        BeanUtils.copyProperties(cartInfo, orderItem);
                        orderItem.setSkuNum(cartInfo.getSkuNum());
                        return orderItem;
                    }).collect(Collectors.toList());
            //订单总金额
            for (OrderItem orderItem : orderItemList) {
                // 计算订单总金额
                totalAmount = totalAmount
                        // 商品单价*购买数量
                        .add(orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())));
            }
        }
        //渲染订单确认页面-生成用户流水号（防止页面重复提交和页面等待超时）
        String tradeNo = this.generateTradeNo(userId);
        //将订单流水号存入数据库payment_info表
//        remotePaymentInfoService.savePaymentInfoTradeNo(tradeNo, SecurityConstants.INNER);
        // 创建一个TradeVo对象来封装交易相关信息
        TradeVo tradeVo = new TradeVo();

        // 设置交易的总金额
        tradeVo.setTotalAmount(totalAmount);
        // 设置交易中的订单项列表
        tradeVo.setOrderItemList(orderItemList);
        // 设置交易编号
        tradeVo.setTradeNo(tradeNo);

        // 返回封装了交易信息的TradeVo对象
        return tradeVo;

    }


    /**
     * 验证页面提交流水号是否有效
     *
     * @param userId
     * @param tradeNo
     * @return
     */
    private Boolean checkTradeNo(String userId, String tradeNo) {
        String userTradeKey = "user:"+ userId+":tradeNo";
        String redisTradeNo = (String) redisTemplate.opsForValue().get(userTradeKey);
        return tradeNo.equals(redisTradeNo);
    }

    /**
     * 删除流水号
     *
     * @param userId
     */
    private void deleteTradeNo(String userId) {
        String userTradeKey = "user:tradeNo:" + userId;
        redisTemplate.delete(userTradeKey);
    }



    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long submitOrder(OrderForm orderForm) {

        //获取用户的userId
        Long userId = SecurityUtils.getLoginUser().getUserid();
        //1 防止订单重复提交
        //判断订单号是否存在：
        // 如果存在则说明用户第一次提交此订单
        // 如果五分中内提交了第二次订单，则订单号不存在，则说明用户已经提交过该订单，不能重复提交
        // 如果五分中之后第一次提交，则订单号也不存在，说明操作超时
    if(!this.checkTradeNo(userId.toString(), orderForm.getTradeNo())){
        throw new ServiceException("请勿重复提交订单");
    }
    //删除redis中的订单号
    this.deleteTradeNo(userId.toString());

        String userTradeKey = "user:"+ userId +":tradeNo" ;

        // 将键的值更为 processed  表示已处理


        //使用lua解决原子性的问题
        String scriptText = """
             if redis.call('get', KEYS[1]) == ARGV[1] then
                 redis.call('set', KEYS[1], 'processed')
                 return 1
             else
                if redis.call('exists', KEYS[1]) == 1 then
                   redis.call('del',KEYS[1])
                   return -1
                else
                   return 0
                end
             end
             """;
        //创建redis脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        //设置脚本内容
        redisScript.setScriptText(scriptText);
        //设置返回值类型为Long
        redisScript.setResultType(Long.class);
        Long flag = (Long)redisTemplate
                // 执行脚本
                .execute(
                        // 脚本
                        redisScript,
                        // 传入的key
                        List.of(userTradeKey),
                        // 传入的参数
                        orderForm.getTradeNo());

        // return 1 表示处理成功
        // return -1  键存在但值不匹配，表示重复提交
        // return 0   键不存在，订单过期
        if (flag == 0) {
            throw new ServiceException("操作超时，请退回重试");
        }
        if(flag == -1){
            throw new ServiceException("请勿重复提交订单");
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        //2 获取最新价格并判断价格是否改变
        //批量获取最新价格
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        List<Long> skuIdList = orderItemList.stream().map(orderItem -> orderItem.getSkuId()).collect(Collectors.toList());
        R<List<SkuPriceVo>> skuPriceListResult =remoteProductService.getSkuPriceList(skuIdList, SecurityConstants.INNER);
        if(R.FAIL == skuPriceListResult.getCode()){
            throw new ServiceException(skuPriceListResult.getMsg());
        }
        // 获取最新价格
        List<SkuPriceVo> skuPriceList = skuPriceListResult.getData();
        // 判断价格是否改变
        String priceCheckResult = "";
        // 将skuId和最新价格封装到map中
        Map<Long, BigDecimal> skuIdSalePriceMap = skuPriceList
                .stream()
                .collect(Collectors.toMap(SkuPriceVo::getSkuId,
                                        SkuPriceVo::getSalePrice));
        for (OrderItem orderItem : orderItemList) {
            // 获取最新价格
            BigDecimal salePrice = skuIdSalePriceMap.get(orderItem.getSkuId());
            // 判断价格是否改变
            if (salePrice.compareTo(orderItem.getSkuPrice()) != 0) {
                priceCheckResult += "商品" + orderItem.getSkuName() + "价格发生变化，请重新下单";
            }
        }
        // 判断价格校验结果是否有值
        if (StringUtils.isNotBlank(priceCheckResult)) {
            if (!orderForm.getIsBuy()) {
                //更新购物车中的价格
                remoteCartService.updateCartPrice(userId, SecurityConstants.INNER);
            }
            // 抛出异常
            throw new ServiceException(priceCheckResult+ "以上商品价格发生变化，请确认");
        }

        //3 校验库存并锁定库存 TODO
        List<SkuLockVo> skuLockVoList = orderItemList.stream().map(item -> {
            SkuLockVo skuLockVo = new SkuLockVo();
            skuLockVo.setSkuId(item.getSkuId());
            skuLockVo.setSkuNum(item.getSkuNum());
            return skuLockVo;
        }).collect(Collectors.toList());
        String checkAndLockResult = remoteSkuStockService.checkAndLock(
                orderForm.getTradeNo(),
                skuLockVoList,
                SecurityConstants.INNER).getData();
        if(StringUtils.isNotEmpty(checkAndLockResult)) {
            throw new ServiceException(checkAndLockResult);
        }

        //4 下单
        Long orderId = null;
        try {
            //下单
            orderId = this.saveOrder(orderForm);
        } catch (Exception e) {
            e.printStackTrace();
            //下单失败，解锁库存
            rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT, MqConst.ROUTING_UNLOCK, orderForm.getTradeNo());
            //抛出异常
            throw new ServiceException("下单失败");
        }


        //5 删除购物车选中商品
        if(!orderForm.getIsBuy()){
            remoteCartService.deleteCartCheckedList(userId, SecurityConstants.INNER);
        }
        return orderId;
    }

    /**
     * 用于处理商品购买逻辑，创建订单并计算总价。
     *
     * @param skuId 商品SKU ID，用于指定购买的商品
     * @return 返回一个TradeVo对象，包含订单信息和交易编号
     */
    @Override
    public TradeVo buy(Long skuId) {

        // 获取商品SKU信息，并检查是否成功获取
        R<ProductSkuVo> productSkuResult = remoteProductService.getProductSku(skuId, SecurityConstants.INNER);

        // 如果获取商品SKU信息失败，抛出服务异常
        if (R.FAIL == productSkuResult.getCode()) {
            throw new ServiceException(productSkuResult.getMsg());
        }

        // 获取商品实时价格，并检查是否成功获取
        R<SkuPriceVo> skuPriceResult = remoteProductService.getSkuPrice(skuId, SecurityConstants.INNER);

        // 如果获取商品实时价格失败，抛出服务异常
        if (R.FAIL == skuPriceResult.getCode()) {
            throw new ServiceException(skuPriceResult.getMsg());
        }

        // 封装商品SKU信息和实时价格数据
        ProductSkuVo productSku = productSkuResult.getData();
        SkuPriceVo skuPrice = skuPriceResult.getData();

        // 创建一个订单项列表，用于存储订单中的所有商品信息
        List<OrderItem> orderItemList = new ArrayList<>();

        // 实例化一个订单项对象，用于封装单个商品的详细信息
        OrderItem orderItem = new OrderItem();

        // 设置订单项的基本信息
        orderItem.setSkuId(skuId); // 设置商品的SKU ID，唯一标识商品的规格
        orderItem.setSkuName(productSku.getSkuName()); // 设置商品的SKU名称，便于在订单中显示商品信息
        orderItem.setSkuNum(1); // 设置商品的购买数量，默认为1，表示购买该商品的数量
        orderItem.setSkuPrice(skuPrice.getSalePrice()); // 设置商品的销售价格，确保订单中的商品价格与当前销售价格保持一致
        orderItem.setThumbImg(productSku.getThumbImg()); // 设置商品的缩略图地址，用于在订单中展示商品图片

        // 将封装好的订单项添加到订单项列表中，表示该商品已被添加到订单
        orderItemList.add(orderItem);

        // 计算订单总金额，使用商品的最新销售价格
        BigDecimal totalAmount = skuPrice.getSalePrice();

        // 渲染订单确认页面 - 生成用户流水号，用于唯一标识此次交易
        String tradeNo = this.generateTradeNo(SecurityUtils.getUserId());

        // 创建并设置返回的TradeVo对象
        TradeVo tradeVo = new TradeVo();
        tradeVo.setTotalAmount(totalAmount); // 设置订单总金额
        tradeVo.setOrderItemList(orderItemList); // 设置订单项列表
        tradeVo.setTradeNo(tradeNo); // 设置交易编号
        tradeVo.setIsBuy(true); // 标记为立即购买

        // 返回包含订单信息和交易编号的TradeVo对象
        return tradeVo;
    }


    @Autowired
    private OrderItemMapper orderItemMapper;
    /**
     * 选择用户订单信息列表
     * 根据用户ID和订单状态查询订单信息，并按创建时间降序排序
     * 如果订单列表不为空，进一步查询每个订单的订单项，并将其设置到对应的订单信息中
     *
     * @param orderStatus 订单状态，用于筛选订单，如果为null，则不进行筛选
     * @return 返回用户订单信息列表，如果无订单，则返回空列表
     */
    @Override
    public List<OrderInfo> selectUserOrderInfoList(Integer orderStatus) {
        // 获取当前登录用户的ID
        Long userId = SecurityUtils.getLoginUser().getUserid();
        // 查询用户订单信息
        List<OrderInfo> orderInfoList = baseMapper
                // 查询条件
                .selectList(new LambdaQueryWrapper<OrderInfo>()
                        // 用户ID
                        .eq(OrderInfo::getUserId, userId)
                        // 订单状态
                        .eq(orderStatus != null, OrderInfo::getOrderStatus, orderStatus)
                        // 按照创建时间排序
                        .orderByDesc(OrderInfo::getCreateTime));
        // 如果订单列表不为空，则进一步查询订单项
        if (!CollectionUtils.isEmpty(orderInfoList)) {
            // 提取订单ID列表
            List<Long> orderIdList = orderInfoList
                    .stream()
                    .map(OrderInfo::getId)
                    .collect(Collectors.toList());
            // 查询orderItem
            List<OrderItem> orderItemList = orderItemMapper
                    .selectList(new LambdaQueryWrapper<OrderItem>()
                            .in(OrderItem::getOrderId, orderIdList));
            // 将orderItemList按照orderId分组
            Map<Long, List<OrderItem>> orderIdToOrderItemListMap = orderItemList
                    .stream()
                    .collect(Collectors.groupingBy(OrderItem::getOrderId));
            // 组装orderItemList
            orderInfoList.forEach(orderInfo -> {
                // 根据orderId获取orderItemList
                List<OrderItem> orderItemListByOrderId = orderIdToOrderItemListMap.get(orderInfo.getId());
                // 设置orderInfo的orderItemList
                orderInfo.setOrderItemList(orderItemListByOrderId);
            });
        }
        // 返回订单信息列表
        return orderInfoList;
    }

    @Override
    public OrderInfo selectOrderInfoById(Long orderId) {
        if (orderId != null) {
            OrderInfo orderInfo = baseMapper.selectById(orderId);
            if (orderInfo != null) {
                LambdaQueryWrapper<OrderItem> orderItemLambdaQueryWrapper = new LambdaQueryWrapper<OrderItem>()
                        .eq(OrderItem::getOrderId, orderId);
                List<OrderItem> orderItemList = orderItemMapper.selectList(orderItemLambdaQueryWrapper);
                orderInfo.setOrderItemList(orderItemList);
                return orderInfo;
            }
        }
        return null;
    }

    /**
     * 取消订单
     * 此方法用于处理用户发起的取消订单请求只有当订单处于待付款状态时，才能取消订单
     *
     * @param orderId 订单ID，用于识别和操作特定的订单记录
     */
    @Override
    public void cancelOrder(Long orderId) {
        // 根据订单ID查询订单信息
        OrderInfo orderInfo = baseMapper.selectById(orderId);
        if (orderInfo != null && orderInfo.getOrderStatus() == 0) {
            // 待付款
            orderInfo.setOrderStatus(-1);//状态-1：已取消
            orderInfo.setCancelTime(new Date());//取消时间
            orderInfo.setCancelReason("用户取消订单");
            baseMapper.updateById(orderInfo);//更新订单信息
            //记录日志
            OrderLog orderLog = new OrderLog();
            orderLog.setOrderId(orderInfo.getId());
            orderLog.setProcessStatus(-1);
            orderLog.setNote("用户取消订单");
            orderLogMapper.insert(orderLog);
            //发送MQ消息通知商品系统解锁库存：TODO
            rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT, MqConst.ROUTING_UNLOCK, orderInfo.getOrderNo());
        }
    }

    @Override
    public OrderInfo getByOrderNo(String orderNo) {
        //订单
        OrderInfo orderInfo = baseMapper.selectOne(
                new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getOrderNo, orderNo)
        );
        //订单详情
        List<OrderItem> orderItemList = orderItemMapper.selectList(
                new LambdaQueryWrapper<OrderItem>()
                        .eq(OrderItem::getOrderId, orderInfo.getId())
        );
        orderInfo.setOrderItemList(orderItemList);
        return orderInfo;
    }

    @Override
    public void processPaySuccess(String orderNo) {
        //获取订单信息
        OrderInfo orderInfo = baseMapper.selectOne(
                new LambdaQueryWrapper<OrderInfo>()
                        .eq(OrderInfo::getOrderNo, orderNo)
//                        .select(OrderInfo::getId, OrderInfo::getOrderStatus)
        );

        //幂等判断
        if(orderInfo.getOrderStatus() == 1) {
            return;
        }
        orderInfo.setOrderStatus(1);//已支付
        orderInfo.setPaymentTime(new Date());
        baseMapper.updateById(orderInfo);
    }


    /**
     * 保存订单信息
     *
     * @param orderForm 订单表单对象，包含用户提交的订单信息
     * @return 返回保存后的订单ID
     * @throws ServiceException 如果获取用户地址信息失败，则抛出服务异常
     */
    private Long saveOrder(OrderForm orderForm) {
        //获取用户id
        Long userId = SecurityUtils.getLoginUser().getUserid();
        //获取用户名
        String userName = SecurityUtils.getLoginUser().getUsername();

        // 保存订单记录：order_info
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setUserId(userId);//下单用户id
        orderInfo.setOrderNo(orderForm.getTradeNo());//订单号
        orderInfo.setNickName(userName);//下单用户名
        orderInfo.setRemark(orderForm.getRemark());//备注
        orderInfo.setFeightFee(orderForm.getFeightFee());//运费

        //远程调用获取用户地址信息
        R<UserAddress> userAddressResult = remoteUserAddressService
                .getUserAddress(orderForm.getUserAddressId(), SecurityConstants.INNER);
        // 判断是否成功
        if(R.FAIL == userAddressResult.getCode()){
            throw new ServiceException(userAddressResult.getMsg());
        }
        //获取用户地址信息
        UserAddress userAddress = userAddressResult.getData();
        //保存用户地址信息
        // 保存用户姓名
        orderInfo.setReceiverName(userAddress.getName());
        // 保存用户电话
        orderInfo.setReceiverPhone(userAddress.getPhone());
        // 保存标签名
        orderInfo.setReceiverTagName(userAddress.getTagName());
        // 保存省id
        orderInfo.setReceiverProvince(userAddress.getProvinceCode());
        // 保存市id
        orderInfo.setReceiverCity(userAddress.getCityCode());
        // 保存区id
        orderInfo.setReceiverDistrict(userAddress.getDistrictCode());
        // 保存详细地址
        orderInfo.setReceiverAddress(userAddress.getFullAddress());
        //实时获取总价格
        //计算orderItem中商品的总价格
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        BigDecimal totalPrice = orderItemList
                //开始流操作
                .stream()
                //获取商品价格，单价乘以数量
                .map(orderItem -> orderItem
                        .getSkuPrice()
                        .multiply(new BigDecimal(orderItem.getSkuNum())))
                //累加（.reduce: 接收两个参数，第一个参数是初始值，第二个参数是累加的函数）
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        //设置商品总价格
        orderInfo.setTotalAmount(totalPrice);
        orderInfo.setCouponAmount(new BigDecimal(0));//优惠券价格
        orderInfo.setOriginalTotalAmount(totalPrice);//原价
        //订单状态
        orderInfo.setOrderStatus(0);//待付款

        orderInfo.setCreateBy(userName);
        orderInfo.setUpdateBy(userName);
        baseMapper.insert(orderInfo);

        // 保存订单项记录：order_item
        orderItemList.forEach(orderItem -> {
            orderItem.setOrderId(orderInfo.getId());
            orderItem.setCreateBy(userName);
            orderItem.setUpdateBy(userName);
            orderItemMapper.insert(orderItem);
        });

        // 保存订单日志记录：order_log
        OrderLog orderLog = new OrderLog();
        orderLog.setOrderId(orderInfo.getId());
        orderLog.setOperateUser(userName);
        orderLog.setProcessStatus(0);
        orderLog.setNote("提交订单");
        orderLog.setCreateBy(userName);
        orderLog.setUpdateBy(userName);
        orderLogMapper.insert(orderLog);

        return orderInfo.getId();
    }

}
